﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using NUnit.Framework;

namespace nunit_demo
{
    /// <summary>
    /// 声明了一个无参数无返回值的委托
    /// </summary>
    public delegate void MyFunc();

    /// <summary>
    /// 声明一个有返回值的委托
    /// </summary>
    /// <returns></returns>
    public delegate int MyFunc2(int value);

    /// <summary>
    /// 自定义实现带泛型的委托.
    /// - 可以发现, 委托可以带泛型也可以带泛型约束
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="K"></typeparam>
    /// <param name="k"></param>
    /// <param name="t"></param>
    /// <returns></returns>
    public delegate T MyFunc3<T, K>(T k, K t) where T : class where K : struct;

    class DelegateClassTest
    {

        [Test]
        public void Test1()
        {
            // 将Func静态函数装载到委托中
            MyFunc f1 = new MyFunc(DelegateClassTest.Func);
            // 调用Invoe方法相当于简介调用传入的函数.
            f1.Invoke();
            // 直接通过括号调用
            f1();

            // 与MyFunc f1 = new MyFunc(WeiTuoTest.Func);意义相同
            MyFunc f2 = DelegateClassTest.Func;
            // 直接通过括号调用
            f2();

            // 有参有返回值的委托使用同理
            MyFunc2 f3 = DelegateClassTest.Func2;
            Console.WriteLine(f3(23));
            Console.WriteLine(f3.Invoke(24));
        }

        [Test]
        public void Test2()
        {
            var c1 = new DelegateClass();
            c1.TestFunc(DelegateClassTest.Func, DelegateClassTest.Func2);
        }

        public static void Func()
        {
            Console.WriteLine("Im Func!");
        }

        public static int Func2(int value)
        {
            return value + 1;
        }

        public static void Func3()
        {
            Console.WriteLine("Im Func3!");
        }

        public static string Func4()
        {
            return "Im Func4!";
        }

        [Test]
        public void Test3()
        {
            // 委托变量存储第一个函数
            MyFunc f1 = DelegateClassTest.Func;
            // 给委托变量增加一个函数
            f1 += DelegateClassTest.Func3;

            // 可以实现的效果是: 这一行代码调用等于调用了传入的所有函数
            f1.Invoke();

            // 注意: 下面的代码会报错
            //MyFunc f2 += WeiTuoTest.Func;
            // 这样写不会报错
            MyFunc f2 = null;
            f2 += DelegateClassTest.Func;
            f2 += DelegateClassTest.Func3;
            f2.Invoke();
        }

        [Test]
        public void Test4()
        {
            var c1 = new DelegateClass();
            c1.AddFunc(DelegateClassTest.Func, DelegateClassTest.Func2);
            c1.AddFunc(DelegateClassTest.Func3, DelegateClassTest.Func2);
            c1.myFunc();
            c1.myFunc2(23);

            // 可以通过-=操作符从委托中删除某一个方法
            c1.RemoveFunc(DelegateClassTest.Func3, DelegateClassTest.Func2);
            c1.myFunc();
            c1.myFunc2(23);

            // 多次重复减不会报错
            c1.RemoveFunc(DelegateClassTest.Func3, DelegateClassTest.Func2);
            c1.RemoveFunc(DelegateClassTest.Func3, DelegateClassTest.Func2);
            c1.RemoveFunc(DelegateClassTest.Func3, DelegateClassTest.Func2);
            // 调用时一定要加上这种为null判断, 否则会报错空指针
            if (c1.myFunc != null)
            {
                c1.myFunc();
            }
            if (c1.myFunc2 != null)
            {
                c1.myFunc2(23);
            }

            // 如果委托为空, 调用会报错, 所以调用时最好加上判断是否为null
        }

        /// <summary>
        /// 使用系统提供的委托:
        /// - Action: 无返回值无参数的委托.
        /// - Func<T>: 待泛型的系统自带委托.
        /// public delegate TResult Func<out TResult>();这种写法是之后的协变和逆变的知识点, 这里的out修饰符的作用是使
        /// 限定返回值的类型.
        /// - 
        /// </summary>
        [Test]
        public void Test5()
        {
            Action action = null;
            action += DelegateClassTest.Func;
            action += DelegateClassTest.Func3;
            action.Invoke();

            Func<string> func = null;
            func += DelegateClassTest.Func4;
            func.Invoke();

            // 系统自带的委托, Action最多带16个参数
            Action<int> ac1 = null;
            Action<int, int> ac2 = null;
            Action<int, int, int> ac3 = null;

            // 同理, 系统自带的Func也有最多16个参数的定义, 最后一个是返回值
            Func<int, int> f1 = null;
            Func<int, int, int, int, int, int, int> f2 = null;
        }
    }

    class DelegateClass
    {
        /// <summary>
        /// 委托类型的变量
        /// </summary>
        public MyFunc myFunc;

        /// <summary>
        /// 还是委托类型的变量
        /// </summary>
        public MyFunc2 myFunc2;

        public void TestFunc(MyFunc myFunc, MyFunc2 myFunc2)
        {
            // 可以先处理一些别的处理, 当这些处理完毕之后, 可以选择是否执行委托中的函数

            // 游戏的相关逻辑
            int i = 8989;

            myFunc.Invoke();
            myFunc2.Invoke(i);

            // 可以把函数传入的委托传递给当前实例的委托字段
            this.myFunc = myFunc;
            this.myFunc2 = myFunc2;
        }

        public void AddFunc(MyFunc myFunc, MyFunc2 myFunc2)
        {
            this.myFunc += myFunc;
            this.myFunc2 += myFunc2;
        }

        public void RemoveFunc(MyFunc myFunc, MyFunc2 myFunc2)
        {
            this.myFunc -= myFunc;
            this.myFunc2 -= myFunc2;
        }

    }

    class EventClass
    {
        /// <summary>
        /// 委托成员变量, 用于存储函数的
        /// </summary>
        public Action action;

        /// <summary>
        /// 事件成员变量, 也是用于存储函数的
        /// - 虽然声明为了public但是还是不能在类外部赋值, 但是可以使用+=和-=
        /// - event只是比delegate更加安全, 其余操作都相同
        /// - 事件只能作为成员变量存在于类和结构及结构体中, 不能作为临时变量
        /// </summary>
        public event Action actionEvent;

        public EventClass()
        {
            // 初始化默认的委托
            this.action = this.Empty;
            // 初始化默认的事件
            this.actionEvent = this.Empty;
        }

        public void Empty()
        {
            Console.WriteLine("empty");
        }

        /// <summary>
        /// 事件只能在函数内部使用
        /// </summary>
        public void HandleEvent()
        {
            this.actionEvent.Invoke();
        }

    }

    class EventClassTest
    {

        [Test]
        public void Test1()
        {
            var e1 = new EventClass();

            e1.action += e1.Empty;
            e1.action -= e1.Empty;

            // 下面的代码会报错, 事件不能在外部赋值
            //e1.actionEvent = e1.Empty;
            e1.actionEvent += e1.Empty;
            e1.actionEvent -= e1.Empty;

            e1.action();
            // 事件只能在声明类的内部调用
            //e1.actionEvent();
            e1.HandleEvent();
        }

    }

    class DelegateMethod
    {
        // 使用系统自带的委托
        public Action action;

        // 匿名函数作为参数传入函数
        public void DoSomthing(int a, Action action)
        {
            Console.WriteLine(a);
            action.Invoke();
        }

        public Action getAction()
        {
            Action acTemp = delegate ()
            {
                Console.WriteLine("函数内部返回的匿名函数!");
            };
            return acTemp; ;
        }

        public static void MyMethod()
        {
            Console.WriteLine("MyMethod函数执行了.");
        }

    }

    class DelegateMethodTest
    {

        [Test]
        public void Test1()
        {
            // 声明一个无参数无返回值的匿名函数并赋值给委托
            Action ac1 = delegate ()
            {
                Console.WriteLine("无参数无返回值的匿名函数!");
            };
            ac1.Invoke();

            // 声明一个有参数无返回值的匿名函数
            Action<int> ac2 = delegate (int id)
            {
                Console.WriteLine("有参数无返回值的匿名函数: " + id);
            };
            ac2.Invoke(222);

            // 声明一个有参数有返回值的匿名函数
            Func<int, int> addOne = delegate (int a)
            {
                return a + 1;
            };
            Console.WriteLine(addOne(2));

            DelegateMethod cm1 = new DelegateMethod();
            cm1.DoSomthing(1, delegate () 
            {
                Console.WriteLine("随函数参数传入的匿名函数逻辑!");
            });

            Action ac3 = cm1.getAction();
            ac3.Invoke();

            // 骚操作之返回匿名函数的函数可以直接调用
            cm1.getAction()();

        }

        /// <summary>
        /// 匿名函数的缺点验证:
        /// - 由于匿名函数没有名字, 无法单独从委托容器中删除
        /// </summary>
        [Test]
        public void Test2()
        {
            // 这种写法也可以用来清空委托中的所有匿名函数
            Action ac1 = null;
            ac1 += delegate ()
            {
                Console.WriteLine("匿名函数1");
            };

            // 这种把匿名函数赋值给变量后将可以根据这个变量来从委托容器中移除指定匿名函数
            Action ac2 = delegate ()
            {
                Console.WriteLine("匿名函数2");
            };

            ac1 += ac2;
            ac1 -= ac2;

            ac1 += DelegateMethod.MyMethod;
            ac1 += DelegateMethod.MyMethod;
            // 具有函数名字的可以显示的进行移除多播
            ac1 -= DelegateMethod.MyMethod;

            ac1.Invoke();
        }

    }


    class LambdaTest
    {

        [Test]
        public void Test1()
        {
            Action ac1 = () =>
            {
                Console.WriteLine("无参无返回值的lambda表达式!");
            };
            ac1();

            Action<int> ac2 = (int a) =>
            {
                Console.WriteLine("有参无返回值的lambda表达式: " + a);
            };
            ac2(2);

            Func<int> ac3 = () =>
            {
                return 33;
            };
            Console.WriteLine(ac3());

            Func<int, int> ac4 = (int a) =>
            {
                return a +1;
            };
            Console.WriteLine(ac4(3));

            Action<int> ac5 = (a) => 
            {
                Console.WriteLine($"缺省类型的lambda, 类型: {a.GetType()}, a: {a}");
            };
            ac5(33);
        }

        /// <summary>
        /// 测试闭包.
        /// 注意:
        /// - lambda函数内部使用的外部变量提供的值并非变量创建时的值, 而是在父函数范围内的最终值.
        /// </summary>
        [Test]
        public void TestBiBao()
        {
            int a = 12;
            Action ac1 = () =>
            {
                Console.WriteLine(a);
            };
            ac1();
            a = 24;
            ac1();
        }
    
    }

    class BiBaoClass
    {

        public event Action action;

        public BiBaoClass()
        {
            // 在当前代码块执行完毕之后, 正常情况下a变量的内存空间就会被回收, 但是lambda函数中使用了这个变量, 形成了闭包, 将会使a变量的内存空间不被回收
            // 想解除闭包可以将action置为null
            // 闭包时, 变量的声明周期被改变了
            int a = 10;
            action = () =>
            {
                // 这里形成了闭包: 内层函数使用了外层函数的变量
                Console.WriteLine(a);
            };

            for (int i = 0; i < 10; i++)
            {
                action += () =>
                {
                    Console.WriteLine(i);
                };
            }

            for (int i = 0; i < 10; i++)
            {
                // 注意的是: 闭包情况下, 循环每次走到这里的index都是不同的, 等于说执行完这个for循环内存中将会出现10个index分别指向0到9
                int index = i;
                action += () =>
                {
                    Console.WriteLine(index);
                };
            }
        }

        public void EventHandle()
        {
            this.action.Invoke();
        }

    }

    class BiBaoClassTest
    {

        [Test]
        public void Test1()
        {
            var obj1 = new BiBaoClass();
            obj1.EventHandle();
        }

    }

}
